/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
package com.facebook.common.executors;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executor;

import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;

/**
 * Simple implementation of delegating Executor that limits concurrency of
 * execution to single thread.
 */
public class SerialDelegatingExecutor implements Executor {

	private final Executor mDelegate;
	@VisibleForTesting
	final Runnable mRunnable;

	/**
	 * True if and only if runnable has been passed to mDelegate for execution,
	 * but the execution itself has not completed yet.
	 */

	@VisibleForTesting
	boolean mExecutionInProgress;

	final private Queue<Runnable> mCommands;

	public SerialDelegatingExecutor(Executor delegate) {
		mDelegate = Preconditions.checkNotNull(delegate);
		mExecutionInProgress = false;
		mCommands = new LinkedList<Runnable>();
		mRunnable = new Runnable() {
			@Override
			public void run() {
				executeSingleCommand();
			}
		};
	}

	/**
	 * Submits another command for execution
	 */
	@Override
	public void execute(Runnable command) {
		synchronized (this) {
			mCommands.add(command);
		}
		maybeSubmitRunnable();
	}

	private void maybeSubmitRunnable() {
		synchronized (this) {
			if (mExecutionInProgress || mCommands.isEmpty()) {
				return;
			}
			mExecutionInProgress = true;
		}
		mDelegate.execute(mRunnable);
	}

	private void executeSingleCommand() {
		Runnable command;
		try {
			removeNextCommand().run();
		} finally {
			clearExecutionInProgress();
			maybeSubmitRunnable();
		}
	}

	private synchronized Runnable removeNextCommand() {
		return mCommands.remove();
	}

	private synchronized void clearExecutionInProgress() {
		mExecutionInProgress = false;
	}
}
